using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace SmartBoard.Core.Networking
{
    /// <summary>
    /// Server socket connector.
    /// </summary>
    public class SocketListener : BaseSocketConnectionCreator
    {
        #region Constructor
        public SocketListener(BaseSocketConnectionHost host, string name, IPEndPoint localEndPoint, EncryptionType encryptionType, CompressionType compressionType, ICryptoService cryptoService, byte backLog, byte acceptThreads)
            : base(host, name, localEndPoint, encryptionType, compressionType, cryptoService)
        {
            BackLog = backLog;
            AcceptThreads = acceptThreads;
        }
        #endregion

        #region Properties

        public byte BackLog
        {
            get; private set;
        }

        public byte AcceptThreads
        {
            get; private set;
        }

        internal Socket Socket
        {
            get; private set;
        }

        public IPEndPoint LocalEndPoint
        {
            get { return InternalLocalEndPoint; }
        }
        #endregion

        #region Public Methods

        public override void Start()
        {
            if (IsDisposed) return;

            Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Socket.Bind(LocalEndPoint);
            Socket.Listen(BackLog * AcceptThreads);

            //----- Begin accept new connections!
            var loopCount = 0;
            SocketAsyncEventArgs e = null;

            for (var i = 1; i <= AcceptThreads; i++)
            {
                e = new SocketAsyncEventArgs
                        {
                            UserToken = this
                        };
                e.Completed += BeginAcceptCallbackAsync;

                if (!Socket.AcceptAsync(e))
                {
                    BeginAcceptCallbackAsync(this, e);
                }

                ThreadUtils.LoopSleep(ref loopCount);
            }
        }

        public override void Stop()
        {
            Dispose();
        }

        #endregion

        #region Private Methods

        private void BeginAcceptCallbackAsync(object sender, SocketAsyncEventArgs e)
        {
            ThreadPool.QueueUserWorkItem(BeginAcceptCallback, e);
        }

        private void BeginAcceptCallback(object state)
        {

            var e = (SocketAsyncEventArgs)state;

            if (!IsDisposed)
            {
                SocketListener listener = null;
                Socket acceptedSocket = null;
                BaseSocketConnection connection = null;

                listener = (SocketListener)e.UserToken;

                if (e.SocketError == SocketError.Success)
                {

                    try
                    {
                        //----- Get accepted socket!
                        acceptedSocket = e.AcceptSocket;

                        //----- Adjust buffer size!
                        acceptedSocket.ReceiveBufferSize = Host.SocketBufferSize;
                        acceptedSocket.SendBufferSize = Host.SocketBufferSize;

                        //----- Initialize!
                        connection = new ServerSocketConnection(Host, listener, acceptedSocket);
                        Host.AddSocketConnection(connection);
                        connection.Active = true;
                        Host.InitializeConnection(connection);

                    }
                    catch
                    {
                        if (connection != null)
                        {
                            if (Host != null)
                            {
                                Host.DisposeConnection(connection);
                                Host.RemoveSocketConnection(connection);
                            }
                            connection = null;
                        }
                    }
                }

                //---- Continue to accept!
                var e2 = new SocketAsyncEventArgs();
                e2.UserToken = listener;
                e2.Completed += new EventHandler<SocketAsyncEventArgs>(BeginAcceptCallbackAsync);

                if (!listener.Socket.AcceptAsync(e2))
                {
                    BeginAcceptCallbackAsync(this, e2);
                }
            }

            e.UserToken = null;
            e.Dispose();
            e = null;
        }
        #endregion

        #region Destructor

        protected override void Free(bool canAccessFinalizable)
        {
            if (Socket != null)
            {
                Socket.Close();
                Socket = null;
            }
            base.Free(canAccessFinalizable);
        }
        #endregion
    }
}